'''
////////////////////////////////////////////////////////////////////
//                            _ooOoo_                             //
//                           o8888888o                            //
//                           88" . "88                            //
//                           (| ^_^ |)                            //
//                           O\  =  /O                            //
//                        ____/`---'\____                         //
//                      .'  \\|     |//  `.                       //
//                     /  \\|||  :  |||//  \                      //
//                    /  _||||| -:- |||||-  \                     //
//                    |   | \\\  -  /// |   |                     //
//                    | \_|  ''\---/''  |   |                     //
//                    \  .-\__  `-`  ___/-. /                     //
//                  ___`. .'  /--.--\  `. . ___                   //
//                ."" '<  `.___\_<|>_/___.'  >'"".                //
//              | | :  `- \`.;`\ _ /`;.`/ - ` : | |               //
//              \  \ `-.   \_ __\ /__ _/   .-` /  /               //
//        ========`-.____`-.___\_____/___.-`____.-'========       //
//                             `=---='                            //
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^      //
//        佛祖保佑             永无BUG              永不修改         //
////////////////////////////////////////////////////////////////////
'''


'''2023工程实践与创新能力大赛全国总决赛 多分类视觉代码'''


'''
自动查找串口设置，避免串口端号在下次拔插时跳变，每次启动程序会重新遍历选取
使用glob.glob查找以/dev/ttyUSB开头的所有串口
这将返回一个列表，其中包含匹配的串口名称
'''
# available_ports = glob.glob('/dev/ttyUSB*')

# if not available_ports:
#     print("没有找到可用的串口。")
# else:
#     # 选择第一个匹配的串口（你可以根据需要修改选择逻辑）
#     selected_port = available_ports[0]
#     print(selected_port)

# serial_port = serial.Serial(
#     port=selected_port,
#     baudrate=38400,
#     bytesize=serial.EIGHTBITS,
#     parity=serial.PARITY_NONE,
#     stopbits=serial.STOPBITS_ONE,
#     timeout=1
# )

'''
    串口指令初始化 具体格式须和电控协商
    本代码串口指令格式,八位16进制码(Hex)
    0x77:启动分拣平台部分舵机
    0x0a:事先规定的垃圾种类,1为可回收垃圾,2为有害垃圾,3为厨余垃圾,4为其他垃圾
    之后的位数在进行倾倒时不是必需数据,因此都给0x00(0)
'''

import sys
import cv2 as cv
import torch
from PyQt5.QtWidgets import QMainWindow, QApplication, QTextEdit
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import QTimer, QThread, pyqtSignal
from main_window1 import Ui_MainWindow      # 导入制作好的UI文件
import serial
import struct
import glob
A = 0x00

Recycle = struct.pack('BBBB BBBB', 0x77, 0x01, A, A, A, A, A, A)

Harmful = struct.pack('BBBB BBBB', 0x77, 0x02, A, A, A, A, A, A)

Kitchen = struct.pack('BBBB BBBB', 0x77, 0x03, A, A, A, A, A, A)

Others = struct.pack('BBBB BBBB', 0x77, 0x04, A, A, A, A, A, A)

# MaxDet = struct.pack('BBBB BBBB', 0x88, A, A, A, A, A, A, A)      # 决赛不进行满载检测


# 通过OpenCV打开的图片(视频)都为BGR格式,要在QT UI上显示,须先转换为RGB格式,再转换为QImage格式
def convert2QImage(img):
    height, width, channel = img.shape
    return QImage(img, width, height, width * channel, QImage.Format_RGB888)


# 串口读写函数

def sent_command(com):
    serial_port.write(com)


def read_mess():
    # 读取一个字节的串口数据
    data = serial_port.read(1)

    # 将接收的数据转换为十六进制
    received_value = int.from_bytes(data, byteorder='big', signed=False)

    return received_value


'''
    垃圾种类计数器,识别到一帧即对应计数器+1,通过给定不同的初始值可实现指令发送的优先级
    此处的优先级为:可回收、厨余、有害、其他
'''
R = -8
H = -16
K = -12
O = -20
cont = 0

# 实时检测线程


class VideoThread(QThread):
    frame_processed = pyqtSignal(QImage)        # 更新视频帧
    text_processed = pyqtSignal(str)            # 更新检测信息
    x1_y1_updated = pyqtSignal(int, int, int, int, int)  # 新的信号用于发送x1, x2, y1, y2, kind的值
    # maxdet_triggered = pyqtSignal()

    def __init__(self, model):
        super().__init__()
        self.model = model
        # global x1,x2,y1,y2
        '''
            初始化物体坐标信息,需要此处的坐标非像素坐标,是由像素转换成步进电机转动所需的脉冲数
            因检测网络输入图片格式为640*640,转换后的脉冲数最大有4位数(16进制)
            故将转换后的x、y值拆分为两个数,每个数有两位
            坐标前的'A'为正负,因电控已实现绝对坐标移动，故此位无效
        '''
        x1 = 0x00
        x2 = 0x00
        y1 = 0x00
        y2 = 0x00
        kind = 0x00
        A = 0x00
        PickUp = struct.pack('BBBB BBBB', 0x66, kind, A, x1, x2, A, y1, y2)

    def run(self):
        global R, H, K, O, PickUp, cont
        self.det = cv.VideoCapture(0)
        self.det.set(cv.CAP_PROP_FRAME_WIDTH, 1280)
        self.det.set(cv.CAP_PROP_FRAME_HEIGHT, 960)
        # print(self.det.get(cv.CAP_PROP_FRAME_WIDTH),
        #       self.det.get(cv.CAP_PROP_FRAME_HEIGHT),
        #       self.det.get(cv.CAP_PROP_FPS),
        #       self.det.get(cv.CAP_PROP_EXPOSURE))

        while True:
            ret, frame = self.det.read()
            if not ret:
                print("Error: Unable to read frame.")
                self.det.release()
                break
            else:
                height, width, _ = frame.shape
                '''
                    原始图像为1280*960，为避免画幅过大造成过多的误识别，进行中心裁剪
                    只取分拣平台附近的640*640画幅
                    原始画幅越大，则中心裁剪后留下的图像越清晰
                '''
                # 计算裁剪的起始坐标
                x = (width - 640) // 2
                y = (height - 560) // 2

                # 进行中心裁剪
                frame = frame[y:y + 640, x:x + 640]

                frame = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
                results = self.model(frame)     # 传入检测网络前要将BGR转换成RGB
                image = results.render()[0]     # 检测结果图像
                # df = results.pandas().xyxy[0]
                # 检测结果将会以pandas数据帧的形式返回，可对其进行排序
                df = results.pandas().xyxy[0].sort_values(by='class', ascending=True)
                self.frame_processed.emit(convert2QImage(image))    # 更新检测结果图像显示

                # detection_texts = []
                # print(df)

                max_num = len(df)       # 获取数据帧长度，可知当前帧有几个垃圾
                # if max_num > 1:

                # print(f"max_num:{max_num}")
                for i, row in df.iterrows():
                    xmin = int(row['xmin'])
                    ymin = int(row['ymin'])
                    xmax = int(row['xmax'])
                    ymax = int(row['ymax'])
                    class_id = int(row['class'])
                    labels = row['name']

                    if max_num > 1:     # 多个垃圾

                        x_center_px = int((xmin + xmax) / 2)
                        y_center_px = int((ymin + ymax) / 2)

                        px2mm = 5 / 12           # 1 px is 5/12 mm
                        px2psc = 2560 / 147      # 1 px is 2560/147 psc  12 px is 5 mm in 1280*960(1280*720)

                        x_length = int(xmax - xmin)
                        y_length = int(ymax - ymin)

                        if x_length > y_length:
                            angle = 0x02
                            px2duo = hex(int((y_length * px2mm - 315.2) / (-3.23)) + 7)
                        else:
                            angle = 0x01
                            px2duo = hex(int((x_length * px2mm - 315.2) / (-3.23)) + 7)

                        # if int(px2duo,16) == 0x66 or int(px2duo,16) == 0x77:
                        #     int(px2duo,16) -= 1

                        # x_center_psc = int(x_center_px * px2psc)+4096*1.2
                        # y_center_psc = int(y_center_px * px2psc)+4096*1.2

                        if y_center_px <= 235:
                            y_center_psc = int(y_center_px * px2psc) + 4096 * 0.6
                            if x_center_px <= 230:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.0

                            elif 230 < x_center_px <= 320:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.5

                            elif 320 < x_center_px <= 420:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.15

                            elif 420 < x_center_px <= 500:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.7

                            elif 500 < x_center_px:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 2.0

                        elif 235 < y_center_px <= 335:
                            y_center_psc = int(y_center_px * px2psc) + 4096 * 1.2
                            if x_center_px <= 230:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.0

                            elif 230 < x_center_px <= 320:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.65

                            elif 320 < x_center_px <= 420:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.15

                            elif 420 < x_center_px <= 500:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 2.1

                            elif 500 < x_center_px:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 2.4

                        elif 335 < y_center_px <= 480:
                            y_center_psc = int(y_center_px * px2psc) + 4096 * 1.80
                            if x_center_px <= 230:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.0

                            elif 230 < x_center_px <= 320:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.65

                            elif 320 < x_center_px <= 420:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.3

                            elif 420 < x_center_px <= 500:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.65

                            elif 500 < x_center_px:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 2.1

                        elif 480 < y_center_px:
                            y_center_psc = int(y_center_px * px2psc) + 4096 * 2.4
                            if x_center_px <= 230:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.0

                            elif 230 < x_center_px <= 320:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 0.55

                            elif 320 < x_center_px <= 420:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.05

                            elif 420 < x_center_px <= 500:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 1.9

                            elif 500 < x_center_px:
                                x_center_psc = int(x_center_px * px2psc) + 4096 * 2.1

                        x_psc1 = hex(int(x_center_psc / 256))
                        x_psc2 = hex(int(x_center_psc % 256))
                        y_psc1 = hex(int(y_center_psc / 256))
                        y_psc2 = hex(int(y_center_psc % 256))

                        # bbox = f"{xmin}  {ymin}  {xmax}  {ymax}"
                        # detection_text = f"{class_id}, {labels} {x_center_px, y_center_px}, {x_psc1, x_psc2, y_psc1, y_psc2}"
                        # detection_texts.append(detection_text)

                        x1 = x_psc1
                        y1 = y_psc1
                        x2 = x_psc2
                        y2 = y_psc2

                        if labels == 'bottle' or labels == 'can' or labels == 'cup':
                            R += 1
                            if R != 0 and R % 25 == 0:
                                cont += 1
                                kind = 0x01
                                PickUp = struct.pack('BBBB BBBB', 0x66, kind, int(x1, 16), int(
                                    x2, 16), int(y1, 16), int(y2, 16), angle, int(px2duo, 16))
                                sent_command(PickUp)
                                H = 0
                                R = 0
                                K = 0
                                O = 0
                                text = f'{cont} 可回收垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print(
                                    f"0x66, {kind}, {x1}, {x2}, {y1}, {y2}, {angle}, {px2duo}, {x_length, y_length}\n{class_id}, {x_center_px, y_center_px}, {x_psc1, x_psc2, y_psc1, y_psc2, angle, px2duo}, {labels}")
                                # time.sleep(2)

                        elif labels == 'potato' or labels == 'turnip' or labels == 'carrot':
                            K += 1
                            if K != 0 and K % 25 == 0:
                                cont += 1
                                kind = 0x03
                                PickUp = struct.pack('BBBB BBBB', 0x66, kind, int(x1, 16), int(
                                    x2, 16), int(y1, 16), int(y2, 16), angle, int(px2duo, 16))
                                sent_command(PickUp)
                                H = 0
                                R = 0
                                K = 0
                                O = 0
                                text = f'{cont} 厨余垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print(
                                    f"0x66, {kind}, {x1}, {x2}, {y1}, {y2}, {angle}, {px2duo}, {x_length, y_length}\n{class_id}, {x_center_px, y_center_px}, {x_psc1, x_psc2, y_psc1, y_psc2, angle, px2duo}, {labels}")
                                # time.sleep(2)

                        elif labels == 'battery':
                            H += 1
                            if H != 0 and H % 25 == 0:
                                cont += 1
                                kind = 0x02
                                PickUp = struct.pack('BBBB BBBB', 0x66, kind, int(x1, 16), int(
                                    x2, 16), int(y1, 16), int(y2, 16), angle, int(px2duo, 16))
                                sent_command(PickUp)
                                H = 0
                                R = 0
                                K = 0
                                O = 0
                                text = f'{cont} 有害垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print(
                                    f"0x66, {kind}, {x1}, {x2}, {y1}, {y2}, {angle}, {px2duo}, {x_length, y_length}\n{class_id}, {x_center_px, y_center_px}, {x_psc1, x_psc2, y_psc1, y_psc2, angle, px2duo}, {labels}")
                                # time.sleep(2)

                        elif labels == 'stone' or labels == 'brick' or labels == 'chip':
                            O += 1
                            if O != 0 and O % 25 == 0:
                                cont += 1
                                kind = 0x04
                                PickUp = struct.pack('BBBB BBBB', 0x66, kind, int(x1, 16), int(
                                    x2, 16), int(y1, 16), int(y2, 16), angle, int(px2duo, 16))
                                sent_command(PickUp)
                                H = 0
                                R = 0
                                K = 0
                                O = 0
                                text = f'{cont} 其他垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print(
                                    f"0x66, {kind}, {x1}, {x2}, {y1}, {y2}, {angle}, {px2duo}, {x_length, y_length}\n{class_id}, {x_center_px, y_center_px}, {x_psc1, x_psc2, y_psc1, y_psc2, angle, px2duo}, {labels}")
                                # time.sleep(2)

                    elif max_num == 1:
                        if labels == 'battery' or labels == 'drug' or labels == 'pill':
                            H += 1
                            self.kind = 0x02
                            if H % 25 == 0:
                                cont += 1
                                sent_command(Harmful)
                                R = -8
                                H = -20
                                K = -12
                                O = -16
                                text = f'{cont} 有害垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print("Harmful sent")

                        if labels == 'bottle' or labels == 'can':
                            R += 1
                            self.kind = 0x01
                            if R % 25 == 0:
                                cont += 1
                                sent_command(Recycle)
                                text = f'{cont} 可回收垃圾 1 OK!\n'
                                R = -8
                                H = -20
                                K = -12
                                O = -16
                                self.text_processed.emit(text)
                                print("Recycle sent")
                                # self.Max_Com()

                        if labels == 'potato' or labels == 'carrot' or labels == 'turnip':
                            K += 1
                            self.kind = 0x03
                            if K % 25 == 0:
                                cont += 1
                                sent_command(Kitchen)
                                R = -8
                                H = -20
                                K = -12
                                O = -16
                                text = f'{cont} 厨余垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print("Kitchen sent")

                        if labels == 'stone' or labels == 'chip' or labels == 'brick':
                            O += 1
                            self.kind = 0x04
                            if O % 25 == 0:
                                cont += 1
                                sent_command(Others)
                                R = -8
                                H = -20
                                K = -12
                                O = -16
                                text = f'{cont} 其他垃圾 1 OK!\n'
                                self.text_processed.emit(text)
                                print("Others sent")

                # self.text_processed.emit("\n".join(detection_texts))    # 合并发送所有检测信息
                # print(len(detection_texts))
                print(f"R:{R}, H:{H}, K:{K}, O:{O}")

                # time.sleep(0.01)


t = 0


class SerialThread(QThread):
    serial_data_received = pyqtSignal(int)

    def __init__(self):
        super().__init__()

    def run(self):
        global t
        while True:
            # t+=1
            # print(f"time: {t}s")
            # if t % 280 == 0:
            #     sent_command(Others)
            received_value = read_mess()
            self.serial_data_received.emit(received_value)
            self.msleep(1000)  # 休眠1秒钟，可以根据需要调整


class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.model = torch.hub.load("./", "custom", path="m_Final_v27.pt", source='local')
        self.model.conf = 0.7
        self.model.iou = 0.3
        self.output_mess = self.findChild(QTextEdit, "output_mess")
        self.maxdet_mess = self.findChild(QTextEdit, "maxdet_mess")

        self.video_thread = VideoThread(self.model)
        self.video_thread.frame_processed.connect(self.update_realtime_det)
        self.video_thread.text_processed.connect(self.show_mess)
        self.video_thread.maxdet_triggered.connect(self.show_maxdet_mess)

        self.video_thread.start()

        self.timer = QTimer()
        self.timer.setInterval(1)
        self.timer.timeout.connect(self.open_video)
        self.det = None

        self.text_style()
        self.video_show()

        self.serial_thread = SerialThread()
        self.serial_thread.serial_data_received.connect(self.handle_serial_data)
        self.serial_thread.start()

    def handle_serial_data(self, received_value):
        # print(f"Received value: 0x{received_value:02X}")  # 打印接收到的十六进制值
        if received_value == 0x11:
            print(f"Received value: 0x{received_value:02X}")  # 打印接收到的十六进制值
            self.show_maxdet_mess()
        elif received_value == 0x99:
            pass
            # sent_command(MaxDet)
            # print("MaxDet sent")

    def open_video(self):
        ret, frame = self.video.read()
        if not ret:
            print("Error: Unable to read frame.")
            self.timer.stop()
        else:
            frame = cv.cvtColor(frame, cv.COLOR_BGR2RGB)
            self.video_player.setPixmap(QPixmap.fromImage(convert2QImage(frame)))

    def video_show(self):
        self.video = cv.VideoCapture('./1_1.mp4')
        if not self.video.isOpened():
            print("Error: Unable to open mp4.")
            return
        else:
            self.timer.start()

    def show_mess(self, text):
        global cont
        self.output_mess.setText(text)
        # if cont == 2:
        #     cont = 0
        #     self.output_mess.clear()

    def text_style(self):
        styleSheet = "QTextEdit{font-size: 50px; color: rgb(100, 200, 0);}"
        maxdet_mess_styleSheet = "QTextEdit{font-size: 50px; color: rgb(255, 0, 0); text-align: center;}"
        self.output_mess.setStyleSheet(styleSheet)
        self.maxdet_mess.setStyleSheet(maxdet_mess_styleSheet)

    def update_realtime_det(self, image):
        self.realtime_det.setPixmap(QPixmap.fromImage(image))

    def show_maxdet_mess(self):
        # self.maxdet_mess.setPlainText("75%满载预警")  # 使用setPlainText设置文本
        self.maxdet_mess.setText("75%满载预警")  # 使用setPlainText设置文本


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()
